/**
 * @file Fault.cc
 * @author Xiaoze Lin (linxiaoze96@gmail.com)
 * @brief The implementation of fault classes.
 * @version 0.1
 *
 * @copyright Copyright (c) 2024
 *
 */
#include "Fault.hh"

namespace ifsim {

Fault::Fault() : _id(-1), _groupId(-1), _collapsedGroupId(-1), _stdId(-1), _stdPinId(-1), _faultClass(UNKNOWN)
{
  ;
}

Fault::Fault(int fault_id, int gid, char pin_id, FaultType type, FaultClass fault_class)
    : _id(fault_id), _gid(gid), _pinId(pin_id), _type(type), _faultClass(fault_class)
{
  ;
}

Fault::Fault(int fault_id, int group_id, int gid, char pin_id, int std_id, char std_pin_id, FaultType type, FaultClass fault_class)
    : _id(fault_id),
      _groupId(group_id),
      _gid(gid),
      _pinId(pin_id),
      _stdId(std_id),
      _stdPinId(std_pin_id),
      _type(type),
      _faultClass(fault_class)
{
  ;
}

Fault::~Fault() = default;

FaultManager::FaultManager() : _numFullFaults(0), _numFaultGroups(0)
{
  _netlistManager = NetlistManager::getOrCreateNetlistManager();
}

FaultManager::FaultManager(AtpgLibrary* atpg_lib) : _numFullFaults(0), _numFaultGroups(0)
{
  _netlistManager = NetlistManager::getOrCreateNetlistManager();
  _atpgLib = atpg_lib;
}

FaultManager::~FaultManager() = default;

/**
 * @brief: Identify full and different class faults.
 */
void FaultManager::identifyFullAndDifferentClassFaults()
{
  // step 1: identify full faults
  identifyFullFaults();

  // step 2: identify equivalent faults
  identifyEquivalentFaults(_fullFaultList);

  // step 3: identify different classes faults
  identifyDifferentClassFaults(_fullFaultList);

  // step 4: identify collapsed faults
  identifyCollapsedFaults();
}

/**
 * @brief: Identify full fault list.
 */
void FaultManager::identifyFullFaults()
{
  LOG_INFO << "identify full faults start";

  int num_nodes = _netlistManager->stdCellNetlist().numNodes();
  const StdCellandIONode* std_nodes = &(_netlistManager->stdCellNetlist().stdCellandIONode()[0]);
  const PrimitiveGate* pri_nodes = &(_netlistManager->primitiveNetlist().nodesConst()[0]);
  const SimGate* sim_nodes = &(_netlistManager->simNetlist().nodes()[0]);
  int num_fault = 0;
  int last_num_fault = 0;
  int num_assign_buf = 0;

  // lambda function
  auto build_new_fault = [this](int fault_id, int gid, int pin_id, int std_id, int std_pin_id, FaultType type, FaultClass fault_class) {
    Fault fault(fault_id, -1, gid, pin_id, std_id, std_pin_id, type, fault_class);
    _fullFaultList.push_back(fault);
    _simGateFaultList[gid].insert(std::pair<char, int>(pin_id, fault_id));
  };

  _simGateFaultList.resize(_netlistManager->simNetlist().numNodes());

  // traverse std nodes
  for (int gid = 0; gid < num_nodes; ++gid) {
    StdCellandIONode std_node = std_nodes[gid];
    auto pri_in_std = std_node.std2Pri();
    last_num_fault = num_fault;

    if (std_node.name() == "ASSIGN_BUF") {  // skip assign stmt added buf
      ++num_assign_buf;
      continue;
    }

    // traverse pri nodes in std node
    for (auto pnode_id : pri_in_std) {
      PrimitiveGate pnode = pri_nodes[pnode_id];
      auto input_mapping = pnode.inputMapping();
      for (int input_i = 0; input_i < input_mapping.size(); ++input_i) {
        int input_pin_id = input_mapping[input_i];
        if (input_pin_id == -1) {
          continue;
        }
        int output_port_cnt = (std_node.type() == STD_OUTPUT) ? 1 : std_node.outPortCnt();  // outPortCnt of OUTPUT is 0
        build_new_fault(num_fault++, pnode.gidSim(), input_i + 1, gid, input_pin_id + output_port_cnt, SA0, UNKNOWN);
        build_new_fault(num_fault++, pnode.gidSim(), input_i + 1, gid, input_pin_id + output_port_cnt, SA1, UNKNOWN);
      }
      int output_pin_id = pnode.outputMapping();
      if (output_pin_id != -1) {
        build_new_fault(num_fault++, pnode.gidSim(), 0, gid, output_pin_id, SA0, UNKNOWN);
        build_new_fault(num_fault++, pnode.gidSim(), 0, gid, output_pin_id, SA1, UNKNOWN);
      }
      if (std_node.type() == STD_INOUT) {  // only build fault in PI node of inout
        break;
      }
    }

    assert((num_fault - last_num_fault == (std_node.inPortCnt() + std_node.outPortCnt()) * 2)
           || (std_node.type() == STD_INOUT && num_fault - last_num_fault == std_node.inPortCnt() * 2));
  }

  numFullFaults(num_fault);
  LOG_INFO << "num of std cell: " << num_nodes - num_assign_buf << ", num of full faults: " << _numFullFaults;

  LOG_INFO << "identify full faults end";
}

/**
 * @brief: Identify equivalent faults.
 */
void FaultManager::identifyEquivalentFaults(std::vector<Fault>& full_fault_list)
{
  LOG_INFO << "identify equivalent faults start";

  const SimGate* sim_nodes = &(_netlistManager->simNetlist().nodes()[0]);
  int num_nodes = _netlistManager->simNetlist().numNodes();
  int first_dla_id = _netlistManager->simNetlist().firstNodeLatch();
  int first_dff_id = _netlistManager->simNetlist().firstNodeDff();
  std::vector<std::vector<Fault>> sim_node_full_fault_list(num_nodes);
  std::vector<std::vector<Fault>> equivalent_faults;

  // lambda function
  auto build_new_group = [&equivalent_faults, &sim_node_full_fault_list](int gid, int pin_fault_id) {
    std::vector<Fault> new_group;
    sim_node_full_fault_list[gid][pin_fault_id].groupId(equivalent_faults.size());  // SA0
    new_group.push_back(sim_node_full_fault_list[gid][pin_fault_id]);
    equivalent_faults.push_back(new_group);
  };

  // lambda function
  auto store_in_exist_group
      = [&equivalent_faults, &sim_node_full_fault_list](int gid, int pin_fault_id, int target_gid, int target_pin_fault_id) {
          int group_id = sim_node_full_fault_list[target_gid][target_pin_fault_id].groupId();
          sim_node_full_fault_list[gid][pin_fault_id].groupId(group_id);
          equivalent_faults[group_id].push_back(sim_node_full_fault_list[gid][pin_fault_id]);
        };

  // lambda function
  auto build_new_group_for_input_pins = [build_new_group, &equivalent_faults, &sim_node_full_fault_list, sim_nodes](int gid) {
    SimGate snode = sim_nodes[gid];
    for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {
      build_new_group(gid, 2 * (pred_i + 1));      // SA0
      build_new_group(gid, 2 * (pred_i + 1) + 1);  // SA1
    }
  };

  // lambda function
  auto store_output_pin_in_exist_group = [store_in_exist_group, sim_nodes](SimGate snode, int gid) {
    int succ_id = snode.succ(0);
    SimGate succ_snode = sim_nodes[succ_id];
    for (int pred_i = 0; pred_i < succ_snode.numPreds(); ++pred_i) {
      if (gid == succ_snode.pred(pred_i)) {
        store_in_exist_group(gid, 0, succ_id, 2 * (pred_i + 1));      // SA0
        store_in_exist_group(gid, 1, succ_id, 2 * (pred_i + 1) + 1);  // SA1
        break;
      }
    }
  };

  // build sim nodes full fault list, faults not in std cell boundary port are set as "_id = -1"
  for (int gid = 0; gid < num_nodes; ++gid) {
    SimGate snode = sim_nodes[gid];
    if (snode.type() == T_TIE1 || snode.type() == T_TIE0 || snode.type() == T_TIEX) {
      continue;
    }
    std::vector<Fault> tmp(2 * (snode.numPreds() + 1));
    if (_simGateFaultList[gid].find(0) == _simGateFaultList[gid].end()) {  // output pin
      Fault fault1(-1, -1, gid, 0, -1, -1, SA0, UNKNOWN);
      Fault fault2(-1, -1, gid, 0, -1, -1, SA1, UNKNOWN);
      tmp[0] = fault1;
      tmp[1] = fault2;
    } else {
      auto iter = _simGateFaultList[gid].equal_range(0).first;
      Fault fault1 = full_fault_list[iter->second];
      iter++;
      Fault fault2 = full_fault_list[iter->second];
      tmp[0] = fault1;
      tmp[1] = fault2;
    }
    for (int i = 0; i < snode.numPreds(); ++i) {
      if (_simGateFaultList[gid].find(i + 1) == _simGateFaultList[gid].end()) {  // input pins
        Fault fault1(-1, -1, gid, i + 1, -1, -1, SA0, UNKNOWN);
        Fault fault2(-1, -1, gid, i + 1, -1, -1, SA1, UNKNOWN);
        tmp[2 * (i + 1)] = fault1;
        tmp[2 * (i + 1) + 1] = fault2;
      } else {
        auto iter = _simGateFaultList[gid].equal_range(i + 1).first;
        Fault fault1 = full_fault_list[iter->second];
        iter++;
        Fault fault2 = full_fault_list[iter->second];
        tmp[2 * (i + 1)] = fault1;
        tmp[2 * (i + 1) + 1] = fault2;
      }
    }
    sim_node_full_fault_list[gid] = tmp;
  }

  for (int i = 0; i < _netlistManager->simNetlist().numDLatches(); ++i) {  // DLA input pins
    int gid = i + first_dla_id;
    build_new_group_for_input_pins(gid);
  }
  for (int i = 0; i < _netlistManager->simNetlist().numDffs(); ++i) {  // DFF input pins
    int gid = i + first_dff_id;
    build_new_group_for_input_pins(gid);
  }

  // start from PO back to PI
  for (int gid = _netlistManager->simNetlist().firstNodePO() + _netlistManager->simNetlist().numPOs() - 1; gid >= 0; gid--) {
    SimGate snode = sim_nodes[gid];
    GType type = snode.type();
    if (type == T_TIE0 || type == T_TIE1 || type == T_TIEX) {
      continue;
    }

    // output pin
    if (snode.numSuccs() == 1) {  // not fanout stem, faults equivalent
      store_output_pin_in_exist_group(snode, gid);
    } else if (type != T_OUTPUT) {  // fanout stem, build two new equivalent groups
      build_new_group(gid, 0);      // SA0
      build_new_group(gid, 1);      // SA1
    }

    // input pins
    switch (type) {
      case T_INPUT: {
        break;
      }
      case T_OUTPUT:
      case T_XOR:
      case T_XNOR:
      case T_MUX_2:
      case T_TRISTATEBUF:
      case T_TRISTATEINV:
      case T_BUS:
      case T_BUFZ: {
        build_new_group_for_input_pins(gid);
        break;
      }
      case T_BUF: {
        store_in_exist_group(gid, 2, gid, 0);  // SA0
        store_in_exist_group(gid, 3, gid, 1);  // SA1
        break;
      }
      case T_NOT: {
        store_in_exist_group(gid, 3, gid, 0);  // SA0
        store_in_exist_group(gid, 2, gid, 1);  // SA1
        break;
      }
      case T_AND: {
        for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {
          store_in_exist_group(gid, 2 * (pred_i + 1), gid, 0);  // SA0, store in equivalent group
          build_new_group(gid, 2 * (pred_i + 1) + 1);           // SA1, build a new equivalent group
        }
        break;
      }
      case T_NAND: {
        for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {
          store_in_exist_group(gid, 2 * (pred_i + 1), gid, 1);  // SA0, store in equivalent group
          build_new_group(gid, 2 * (pred_i + 1) + 1);           // SA1, build a new equivalent group
        }
        break;
      }
      case T_OR: {
        for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {
          build_new_group(gid, 2 * (pred_i + 1));                   // SA0, build a new equivalent group
          store_in_exist_group(gid, 2 * (pred_i + 1) + 1, gid, 1);  // SA1, store in equivalent group
        }
        break;
      }
      case T_NOR: {
        for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {
          build_new_group(gid, 2 * (pred_i + 1));                   // SA0, build a new equivalent group
          store_in_exist_group(gid, 2 * (pred_i + 1) + 1, gid, 0);  // SA1, store in equivalent group
        }
        break;
      }
      case T_DLA_PRI:
      case T_DFF_PRI: {
        break;
      }
      default: {
        LOG_ERROR << "unsupported gate type " << LUT_GTypeToName[type];
        break;
      }
    }
  }

  for (int i = 0; i < _netlistManager->simNetlist().numDLatches(); ++i) {  // DLA output pin
    int gid = i + first_dla_id;
    SimGate snode = sim_nodes[gid];
    if (snode.numSuccs() == 1) {  // not fanout stem, faults equivalent
      store_output_pin_in_exist_group(snode, gid);
    } else {  // fanout stem, build two new equivalent groups
      build_new_group(gid, 0);
      build_new_group(gid, 1);
    }
  }
  for (int i = 0; i < _netlistManager->simNetlist().numDffs(); ++i) {  // DFF output pin
    int gid = i + first_dff_id;
    SimGate snode = sim_nodes[gid];
    if (snode.numSuccs() == 1) {  // not fanout stem, faults equivalent
      store_output_pin_in_exist_group(snode, gid);
    } else {  // fanout stem, build two new equivalent groups
      build_new_group(gid, 0);
      build_new_group(gid, 1);
    }
  }

  // store those faults in std cell boundary pin into relevant equivalent groups
  for (auto group : equivalent_faults) {
    std::vector<Fault> tmp;
    for (auto fault : group) {
      if (fault.faultId() == -1) {  // not std cell boundary fault
        continue;
      }
      int group_id = _equivalentFaultGroup.size();
      fault.groupId(group_id);  // update group id
      full_fault_list[fault.faultId()].groupId(group_id);
      fault.faultClass(EQ);  // update fault class
      tmp.push_back(fault);
    }
    if (!tmp.empty()) {            // there are faults in std cell boundary pin in current group
      tmp[0].faultClass(UNKNOWN);  // the first fault in every group is the representative
      _equivalentFaultGroup.push_back(tmp);
    }
  }

  // update fault group id in _simGateFaultList
  for (auto& pin_id_and_faults_pair : _simGateFaultList) {
    for (auto iter = pin_id_and_faults_pair.begin(); iter != pin_id_and_faults_pair.end(); ++iter) {
      Fault fault = full_fault_list[iter->second];
      iter->second = fault.groupId();
    }
  }

  int num = 0;
  for (auto group : _equivalentFaultGroup) {
    for (auto fault : group) {
      num++;
    }
  }
  LOG_INFO << "num of collapsed faults: " << _equivalentFaultGroup.size() << ", num of total faults: " << num;
  LOG_INFO << "identify equivalent faults end";
}

/**
 * @brief: Identify untestable faults.
 */
void FaultManager::identifyDifferentClassFaults(const std::vector<Fault>& full_fault_list)
{
  LOG_INFO << "identify different classes faults start";

  // 1. identify unused faults
  identifyUnusedFaults();

  // 2. identify tied and blocked faults
  identifyTiedFaults();
  identifyBlockedFaults();

  LOG_INFO << "identify different classes faults end";
}

/**
 * @brief: Identify unused faults(UU).
 */
void FaultManager::identifyUnusedFaults()
{
  const SimGate* sim_nodes = &(_netlistManager->simNetlist().nodes()[0]);
  int num_nodes = _netlistManager->simNetlist().numNodes();

  for (int gid = 0; gid < num_nodes; ++gid) {
    SimGate snode = sim_nodes[gid];
    if (snode.numSuccs() > 0) {
      continue;
    } else if (snode.type() == T_OUTPUT) {
      StdCellandIONode std_node = _netlistManager->stdCellNetlist().stdCellandIONode()[snode.gidStd()];
      if (std_node.predId(0)[0] == tiex) {
        auto pr = _simGateFaultList[gid].equal_range(1);  // input pin of PO
        for (auto iter = pr.first; iter != pr.second; ++iter) {
          _unusedFaultGroups.insert(iter->second);
          _equivalentFaultGroup[iter->second][0].faultClass(UU);  // change the fault type of the representative
        }
      }
      continue;
    }
    auto pr = _simGateFaultList[gid].equal_range(0);  // output pin of sim node
    for (auto iter = pr.first; iter != pr.second; ++iter) {
      _unusedFaultGroups.insert(iter->second);
      _equivalentFaultGroup[iter->second][0].faultClass(UU);  // change the fault type of the representative
    }
  }

  int cnt = 0;
  for (auto group : _unusedFaultGroups) {
    cnt += _equivalentFaultGroup[group].size();
  }
  LOG_INFO << "num of UU fault groups: " << _unusedFaultGroups.size() << ", num of UU faults: " << cnt;
}

/**
 * @brief: Identify tied and blocked faults based on tie nodes
 */
void FaultManager::identifyTiedFaults()
{
  const SimGate* sim_nodes = &(_netlistManager->simNetlist().nodes()[0]);
  auto tie0s = _netlistManager->simNetlist().gatesTie0();
  auto tie1s = _netlistManager->simNetlist().gatesTie1();
  auto tiezs = _netlistManager->simNetlist().gatesTiez();

  // lambda function
  auto find_tie0_faults = [this](int gid, int pin_id) {
    auto pr = this->_simGateFaultList[gid].equal_range(pin_id);
    if (!(pr.first == pr.second)) {
      this->_tiedFaultGroups.insert(pr.first->second);  // only SA0 fault
      this->_equivalentFaultGroup[pr.first->second][0].faultClass(TI);
    }
  };

  // lambda function
  auto find_tie1_faults = [this](int gid, int pin_id) {
    auto pr = this->_simGateFaultList[gid].equal_range(pin_id);
    if (!(pr.first == pr.second)) {
      auto iter = pr.first;
      iter++;
      this->_tiedFaultGroups.insert(iter->second);  // only SA1 fault
      this->_equivalentFaultGroup[iter->second][0].faultClass(TI);
    }
  };

  // lambda function
  auto find_tiez_faults = [this](int gid, int pin_id) {
    auto pr = this->_simGateFaultList[gid].equal_range(pin_id);
    if (!(pr.first == pr.second)) {
      this->_tiedFaultGroups.insert(pr.first->second);  //  SA0 fault
      this->_equivalentFaultGroup[pr.first->second][0].faultClass(TI);
      auto iter = pr.first;
      iter++;
      this->_tiedFaultGroups.insert(iter->second);  //  SA1 fault
      this->_equivalentFaultGroup[iter->second][0].faultClass(TI);
    }
  };

  for (auto tie0_id : tie0s) {  // tie0 nodes
    SimGate snode = sim_nodes[tie0_id];
    find_tie0_faults(tie0_id, 0);  // output pin
    if (snode.type() == T_AND || snode.type() == T_OR) {
      for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {  // input val = 0
        int pred_id = snode.pred(pred_i);
        if (tie0s.find(pred_id) != tie0s.end()) {
          find_tie0_faults(tie0_id, pred_i + 1);
        }
      }
    } else if (snode.type() == T_NAND || snode.type() == T_NOR || snode.type() == T_NOT) {
      for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {  // input val = 1
        int pred_id = snode.pred(pred_i);
        if (tie1s.find(pred_id) != tie1s.end()) {
          find_tie1_faults(tie0_id, pred_i + 1);
        }
      }
    }
    for (auto succ_id : snode.succs()) {  // input pin of succs
      SimGate succ_snode = sim_nodes[succ_id];
      for (int pred_succ_i = 0; pred_succ_i < succ_snode.numPreds(); ++pred_succ_i) {
        int pred_succ_id = succ_snode.pred(pred_succ_i);
        if (tie0_id == pred_succ_id) {
          find_tie0_faults(succ_id, pred_succ_i + 1);
          break;
        }
      }
    }
  }

  for (auto tie1_id : tie1s) {  // tie1 nodes
    SimGate snode = sim_nodes[tie1_id];
    find_tie1_faults(tie1_id, 0);  // output pin
    if (snode.type() == T_AND || snode.type() == T_OR) {
      for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {  // input val = 1
        int pred_id = snode.pred(pred_i);
        if (tie1s.find(pred_id) != tie1s.end()) {
          find_tie1_faults(tie1_id, pred_i + 1);
        }
      }
    } else if (snode.type() == T_NAND || snode.type() == T_NOR || snode.type() == T_NOT) {
      for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {  // input val = 0
        int pred_id = snode.pred(pred_i);
        if (tie0s.find(pred_id) != tie0s.end()) {
          find_tie0_faults(tie1_id, pred_i + 1);
        }
      }
    }
    for (auto succ_id : snode.succs()) {  // input pin of succs
      SimGate succ_snode = sim_nodes[succ_id];
      for (int pred_succ_i = 0; pred_succ_i < succ_snode.numPreds(); ++pred_succ_i) {
        int pred_succ_id = succ_snode.pred(pred_succ_i);
        if (tie1_id == pred_succ_id) {
          find_tie1_faults(succ_id, pred_succ_i + 1);
          break;
        }
      }
    }
  }

  for (auto tiez_id : tiezs) {  // tiez nodes
    SimGate snode = sim_nodes[tiez_id];
    find_tiez_faults(tiez_id, 0);  // output pin

    for (auto succ_id : snode.succs()) {  // input pin of succs
      SimGate succ_snode = sim_nodes[succ_id];
      for (int pred_succ_i = 0; pred_succ_i < succ_snode.numPreds(); ++pred_succ_i) {
        int pred_succ_id = succ_snode.pred(pred_succ_i);
        if (tiez_id == pred_succ_id) {
          find_tiez_faults(succ_id, pred_succ_i + 1);
          break;
        }
      }
    }
  }

  int cnt = 0;
  for (auto group : _tiedFaultGroups) {
    cnt += _equivalentFaultGroup[group].size();
  }
  LOG_INFO << "num of TI fault groups: " << _tiedFaultGroups.size() << ", num of TI faults: " << cnt;
}

/**
 * @brief: Identify blocked faults.
 */
void FaultManager::identifyBlockedFaults()
{
  const SimGate* sim_nodes = &(_netlistManager->simNetlist().nodes()[0]);
  auto tie0s = _netlistManager->simNetlist().gatesTie0();
  auto tie1s = _netlistManager->simNetlist().gatesTie1();
  std::unordered_map<int, int> blocked_nodes;  // blocking nodes leads to unobservability faults
  std::queue<int> event_queue;

  // lambda function
  auto find_blocked_faults = [this](int gid, int pin_id) {
    auto pr = this->_simGateFaultList[gid].equal_range(pin_id);
    for (auto iter = pr.first; iter != pr.second; ++iter) {
      if (this->_equivalentFaultGroup[iter->second][0].faultClass() == UNKNOWN) {
        this->_blockedFaultGroups.insert(iter->second);
        this->_equivalentFaultGroup[iter->second][0].faultClass(BL);
      }
    }
  };

  for (auto tie0_id : tie0s) {  // tie0 nodes
    SimGate snode = sim_nodes[tie0_id];
    for (auto succ_id : snode.succs()) {
      SimGate succ_snode = sim_nodes[succ_id];
      if (succ_snode.type() == T_AND || succ_snode.type() == T_NAND) {
        for (int pred_succ_i = 0; pred_succ_i < succ_snode.numPreds(); ++pred_succ_i) {
          int pred_succ_id = succ_snode.pred(pred_succ_i);
          if (tie0_id == pred_succ_id) {
            continue;
          }
          find_blocked_faults(succ_id, pred_succ_i + 1);
          blocked_nodes.insert(std::pair<int, int>(succ_id, snode.gid()));
          event_queue.push(pred_succ_id);
        }
      } else if (succ_snode.type() == T_TRISTATEBUF || succ_snode.type() == T_TRISTATEINV) {
        int oe_id = succ_snode.pred(0);
        if (oe_id == tie0_id) {  // oe port tie at 0, so in port is blocked
          find_blocked_faults(succ_id, 1 + 1);
          blocked_nodes.insert(std::pair<int, int>(succ_id, snode.gid()));
          event_queue.push(succ_snode.pred(1));
        }
      }
    }
  }
  for (auto tie1_id : tie1s) {  // tie1 nodes
    SimGate snode = sim_nodes[tie1_id];
    for (auto succ_id : snode.succs()) {
      SimGate succ_snode = sim_nodes[succ_id];
      if (succ_snode.type() == T_OR || succ_snode.type() == T_NOR) {
        for (int pred_succ_i = 0; pred_succ_i < succ_snode.numPreds(); ++pred_succ_i) {
          int pred_succ_id = succ_snode.pred(pred_succ_i);
          if (tie1_id == pred_succ_id) {
            continue;
          }
          find_blocked_faults(succ_id, pred_succ_i + 1);
          blocked_nodes.insert(std::pair<int, int>(succ_id, snode.gid()));
          event_queue.push(pred_succ_id);
        }
      }
    }
  }

  while (!event_queue.empty()) {
    int gid = event_queue.front();
    event_queue.pop();
    SimGate snode = sim_nodes[gid];
    if (snode.numSuccs() == 1) {
      find_blocked_faults(gid, 0);
      blocked_nodes.insert(std::pair<int, int>(gid, -1));
      for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {
        find_blocked_faults(gid, pred_i + 1);
        event_queue.push(snode.pred(pred_i));
      }
    } else {
      bool is_blocked = true;
      for (auto succ_id : snode.succs()) {
        if (blocked_nodes.find(succ_id) == blocked_nodes.end()) {
          is_blocked = false;
          break;
        } else if (blocked_nodes[succ_id] == gid) {
          is_blocked = false;
          break;
        }
      }
      if (is_blocked) {
        find_blocked_faults(gid, 0);
        blocked_nodes.insert(std::pair<int, int>(gid, -1));
        for (int pred_i = 0; pred_i < snode.numPreds(); ++pred_i) {
          find_blocked_faults(gid, pred_i + 1);
          event_queue.push(snode.pred(pred_i));
        }
      }
    }
  }

  int cnt = 0;
  for (auto group : _blockedFaultGroups) {
    cnt += _equivalentFaultGroup[group].size();
  }
  LOG_INFO << "num of BL fault groups: " << _blockedFaultGroups.size() << ", num of BL faults: " << cnt;
}

/**
 * @brief: Build collapsed fault list.
 */
void FaultManager::identifyCollapsedFaults()
{
  for (int group_id = 0; group_id < _equivalentFaultGroup.size(); ++group_id) {
    if (_equivalentFaultGroup[group_id][0].faultClass() != UNKNOWN) {
      continue;
    }
    _collapsedFaultGroup.push_back(group_id);
    _equivalentFaultGroup[group_id][0].collapsedGroupId(_collapsedFaultGroup.size() - 1);
  }

  int cnt = 0;
  for (auto group : _collapsedFaultGroup) {
    cnt += _equivalentFaultGroup[group].size();
  }
  LOG_INFO << "num of collapsed undetected faults: " << _collapsedFaultGroup.size() << ", num of total undetected faults: " << cnt;
}

/**
 * @brief: Read tmax fault list.
 */
bool FaultManager::readFaultList(const char* fault_list_file)
{
  std::ifstream file(fault_list_file);
  if (!file) {
    LOG_ERROR << "fault list file: " << fault_list_file << " is not exist";
    return false;
  }

  std::vector<std::pair<std::string, std::string>> inst_name_and_port_name;
  std::vector<std::pair<FaultType, FaultClass>> fault_type_and_fault_class;
  std::string line;

  // read fault list file
  while (getline(file, line)) {
    int i = 0;
    while (line[i] == 's' || line[i] == 'a') {  // get fault type
      ++i;
    }
    FaultType type = (line[i] == '1') ? SA1 : SA0;
    ++i;

    int fault_class_start = 0, fault_class_end = 0;  // get fault class
    while (line[i] == ' ') {
      ++i;
    }
    fault_class_start = i;
    while (line[i] != ' ') {
      ++i;
    }
    fault_class_end = i;
    std::string fault_class_str = line.substr(fault_class_start, fault_class_end - fault_class_start);
    FaultClass fault_class = LUT_ToStringFaultClass[fault_class_str];
    ++i;

    int std_inst_name_start = 0, std_inst_name_end = 0;  // get std inst name and pin name
    while (line[i] == ' ' || line[i] == '"') {
      ++i;
    }
    std_inst_name_start = i;
    while (line[i] != '.' && (i <= line.size() - 1)) {
      ++i;
    }
    int last_i = i;
    while (line[i - 1] == ' ') {  // rollback to skip ' '
      i--;
    }
    std_inst_name_end = i;
    i = last_i;
    std::string std_inst_name = line.substr(std_inst_name_start, std_inst_name_end - std_inst_name_start);
    if (i < (line.size() - 1)) {
      ++i;  // skip the '.'
    }
    std::string pin_name = "";
    if (line[line.size() - 1] == '"') {  // skip the final '"'
      pin_name = line.substr(i, line.size() - i - 1);
    } else {
      pin_name = line.substr(i, line.size() - i);
    }

    inst_name_and_port_name.push_back(std::pair<std::string, std::string>(std_inst_name, pin_name));
    fault_type_and_fault_class.push_back(std::pair<FaultType, FaultClass>(type, fault_class));
  }
  file.close();

  // store fault list
  std::vector<Fault> one_group;
  int fault_id = 0;
  auto std_nodes = _netlistManager->stdCellNetlist().stdCellandIONode();
  auto pri_nodes = _netlistManager->primitiveNetlist().nodesConst();
  auto std_inst_name_and_std_id = _netlistManager->stdCellNetlist().stdInstNameAndStdId();
  for (int i = 0; i < inst_name_and_port_name.size(); ++i) {
    if (fault_type_and_fault_class[i].second != EQ && i != 0) {
      _readFaultList.push_back(one_group);
      one_group.clear();
    }
    Fault fault;
    fault.faultId(fault_id++);                               // fault id
    fault.faultType(fault_type_and_fault_class[i].first);    // fault type
    fault.faultClass(fault_type_and_fault_class[i].second);  // fault class

    std::string inst_name = inst_name_and_port_name[i].first;
    std::string port_name = inst_name_and_port_name[i].second;
    if (std_inst_name_and_std_id.find(inst_name) == std_inst_name_and_std_id.end()) {
      LOG_ERROR << "inst name: " << inst_name << " in fault list " << fault_list_file << " is not found in std netlist.";
      return false;
    }
    int std_id = std_inst_name_and_std_id[inst_name];
    fault.stdId(std_id);  // std id

    StdCellandIONode std_node = std_nodes[std_id];

    if (std_node.type() == STD_INPUT) {
      fault.stdPinId(0);  // std port id
      int pri_id = std_node.std2Pri(0);
      fault.gid(pri_nodes[pri_id].gidSim());
      fault.pinId(0);

    } else if (std_node.type() == STD_OUTPUT) {
      fault.stdPinId(1);  // std port id
      int pri_id = std_node.std2Pri(0);
      fault.gid(pri_nodes[pri_id].gidSim());
      fault.pinId(1);

    } else if (std_node.type() == STD_INOUT) {
      fault.stdPinId(0);                 // std port id
      int pri_id = std_node.std2Pri(0);  // PI node
      fault.gid(pri_nodes[pri_id].gidSim());
      fault.pinId(0);

    } else if (std_node.type() == STD_CELL) {
      std::string std_cell_name = std_node.stdCellName();
      int port_id = -1;
      auto inport_name = std_node.inPortName();
      for (int inport_i = 0; inport_i < inport_name.size(); ++inport_i) {  // find in inport name
        if (port_name == inport_name[inport_i]) {
          port_id = inport_i + std_node.outPortName().size();
          auto pri_id_and_pin_id = std_node.inputMappingPriId(inport_i);
          fault.gid(pri_nodes[pri_id_and_pin_id.first].gidSim());
          fault.pinId(pri_id_and_pin_id.second + 1);
          break;
        }
      }
      if (port_id == -1) {  // find in outport name
        auto outport_name = std_node.outPortName();
        for (int outport_i = 0; outport_i < outport_name.size(); ++outport_i) {
          if (port_name == outport_name[outport_i]) {
            port_id = outport_i;
            fault.gid(pri_nodes[std_node.outputMappingPriId(outport_i)].gidSim());
            fault.pinId(0);
            break;
          }
        }
      }
      if (port_id == -1) {  // not find port name
        LOG_ERROR << "can not find port " << port_name << " in std cell " << std_cell_name;
        return false;
      }
      fault.stdPinId(port_id);  // std port id

    } else {
      LOG_ERROR << "not supported std cell type " << std_node.type() << " in reading fault list";
    }
    one_group.push_back(fault);
  }
  _readFaultList.push_back(one_group);

  int cnt = 0;
  for (auto group : _readFaultList) {
    cnt += group.size();
  }
  LOG_INFO << "num of read fault groups: " << _readFaultList.size() << ", num of read faults: " << cnt;

  return true;
}

/**
 * @brief: Calculate untestable fault num.
 */
int FaultManager::numUntestableFaults()
{
  int num = 0;
  for (auto group_id : _unusedFaultGroups) {
    num += _equivalentFaultGroup[group_id].size();
  }
  for (auto group_id : _tiedFaultGroups) {
    num += _equivalentFaultGroup[group_id].size();
  }
  for (auto group_id : _blockedFaultGroups) {
    num += _equivalentFaultGroup[group_id].size();
  }
  return num;
}

/**
 * @brief: Calculate unused fault num.
 */
int FaultManager::numUnusedFaults()
{
  int num = 0;
  for (auto group_id : _unusedFaultGroups) {
    num += _equivalentFaultGroup[group_id].size();
  }
  return num;
}

/**
 * @brief: Calculate tied fault num.
 */
int FaultManager::numTiedFaults()
{
  int num = 0;
  for (auto group_id : _tiedFaultGroups) {
    num += _equivalentFaultGroup[group_id].size();
  }
  return num;
}

/**
 * @brief: Calculate blocked fault num.
 */
int FaultManager::numBlockedFaults()
{
  int num = 0;
  for (auto group_id : _blockedFaultGroups) {
    num += _equivalentFaultGroup[group_id].size();
  }
  return num;
}
};  // namespace ifsim